#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <string>
#include <set>
#include <map>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <numeric>
#include <ctime>
#include <fstream>
using namespace std;

#define sqr(a) ((a)*(a))

const double TIME_LIMIT = 8.0 * CLOCKS_PER_SEC;
const string GIVE_UP = "GIVE UP";
int di[] = {-1, 0, 0, 1};
int dj[] = {0, -1, 1, 0};

class PointSet {
	set<pair<int, int> > m_set;
public:
	bool add(int x, int y) {
		return m_set.insert(make_pair(x, y)).second;
	}
	bool check(int x, int y) {
		return m_set.find(make_pair(x, y)) != m_set.end();
	}
};

struct Cell {
	int row;
	int col;

	Cell(int row, int col) : row(row), col(col)
	{
	}

	bool operator < (const Cell &other) const {
		if (row != other.row) {
			return row < other.row;
		}
		return col < other.col;
	}
};
typedef vector<int> Tile;

struct Path {
	int source;
	int target;
	int length;

	Path ()
		: source(-1), target(-1), length(-1)
	{
	}

	Path (int source, int target, int length)
		: source(source), target(target), length(length)
	{
	}
};

class TwistedGame {
	int m_N;
	Tile m_firstTile;
	int m_step;
	int m_path0, m_path1;
	PointSet m_used;
	vector<Path> m_paths;
	map<int, int> m_vToPath;
public:
	int init(int N, Tile firstTile) {
		this->m_N = N;
		this->m_firstTile = firstTile;
		m_step = 0;
		addEdges(N, N, 0, firstTile);
		m_used.add(N, N);
		return 0;
	}
	string placeTile(Tile tile) {
		++m_step;
		if (m_step % (1 << 7) == 0 && clock() >= TIME_LIMIT) {
			cerr << "time = " << clock() << endl;
			cerr << "break by timeout!" << endl;
			return GIVE_UP;
		}
		int row = -1;
		int col = -1;
		int rot = 0;
		int idx = -1;
		if (m_step == 1) {
			row = m_N;
			col = m_N + 1;
			getNodes(m_N, m_N, row, col, m_path0, m_path1);
			for (int nRot = 0; nRot < 4; ++nRot) {
				if (getCyclesAmount(row, col, nRot, tile) == 0) {
					rot = nRot;
					break;
				}
			}
		} else {
			if (!findNextMove(row, col, rot, tile)) {
				return GIVE_UP;
			}
		}
		if (row != -1 && col != -1 && rot != -1) {
			addEdges(row, col, rot, tile);
			m_used.add(row, col);
		} else {
			cerr << "can't continue" << endl;
		}
		return makeString(row, col, rot);
	}
private:
	int getPath(int v, map<int, int> &vToPath, vector<Path> &paths) {
		if (vToPath.find(v) == vToPath.end()) {
			paths.push_back(Path(v, v, 0));
			vToPath[v] = (int)paths.size() - 1;
		}
		return vToPath[v];
	}
	bool isMergeTwoFibres(int row, int col, int rot, const Tile &tile) {
		if (m_path0 == m_path1) {
			return false;
		}
		for (int i = 0; i < 4; ++i) {
			int idx0 = rotate(tile[2 * i], rot);
			int idx1 = rotate(tile[2 * i + 1], rot);
			int xPath = getPath(getNode(row, col, idx0), m_vToPath, m_paths);
			int yPath = getPath(getNode(row, col, idx1), m_vToPath, m_paths);
			if (xPath == m_path0 && yPath == m_path1
				|| xPath == m_path1 && yPath == m_path0) {
					return true;
			}
		}
		return false;
	}

	int getPathInLocalVersion(int v, map<int, int> &vToPath, vector<Path> &paths) {
		if (vToPath.find(v) == vToPath.end()) {
			int vPath = getPath(v, m_vToPath, m_paths);
			paths.push_back(m_paths[vPath]);
			vToPath[m_paths[vPath].source] = (int)paths.size() - 1;
			vToPath[m_paths[vPath].target] = (int)paths.size() - 1;
		}
		return vToPath[v];
	}

	int getCyclesAmount(int row, int col, int rot, const Tile &tile) {
		int res = 0;
		map<int, int> vToPath;
		vector<Path> paths;
		int path0 = getPathInLocalVersion(m_paths[m_path0].source, vToPath, paths);
		int path1 = getPathInLocalVersion(m_paths[m_path1].source, vToPath, paths);
		for (int i = 0; i < 4; ++i) {
			int idx0 = rotate(tile[2 * i], rot);
			int idx1 = rotate(tile[2 * i + 1], rot);
			int x = getNode(row, col, idx0);
			int y = getNode(row, col, idx1);
			int xPath = getPathInLocalVersion(x, vToPath, paths);
			int yPath = getPathInLocalVersion(y, vToPath, paths);
			if (xPath == yPath && (xPath == path0 || xPath == path1)) {
				return 100;
			}
			if (xPath == yPath) {
				++res;
			}
			mergePaths(x, y, vToPath, paths, path0, path1);
		}
		return res;
	}

	int getMaxDist(int row, int col, int rot, const Tile &tile) {
		map<int, int> vToPath;
		vector<Path> paths;
		int path0 = getPathInLocalVersion(m_paths[m_path0].source, vToPath, paths);
		int path1 = getPathInLocalVersion(m_paths[m_path1].source, vToPath, paths);
		for (int i = 0; i < 4; ++i) {
			int idx0 = rotate(tile[2 * i], rot);
			int idx1 = rotate(tile[2 * i + 1], rot);
			int x = getNode(row, col, idx0);
			int y = getNode(row, col, idx1);
			int xPath = getPathInLocalVersion(x, vToPath, paths);
			int yPath = getPathInLocalVersion(y, vToPath, paths);
			mergePaths(x, y, vToPath, paths, path0, path1);
		}
		vector<int> vertices;
		vertices.push_back(paths[path0].source);
		vertices.push_back(paths[path0].target);
		vertices.push_back(paths[path1].source);
		vertices.push_back(paths[path1].target);
		int res = 0;
		for (int i = 0; i < vertices.size(); ++i) {
			for (int j = i + 1; j < vertices.size(); ++j) {
				int row1, col1, idx1;
				getCell(vertices[i], row1, col1, idx1);
				int row2, col2, idx2;
				getCell(vertices[j], row2, col2, idx2);
				int dist = abs(row1 - row2) + abs(col1 - col2);
				res = max(res, dist);
			}
		}
		return res;
	}

	bool isTurnLeft(int row, int col, int rot, const Tile &tile) {
		for (int idx = 0; idx < 8; ++idx) {
			int path = getPath(getNode(row, col, idx), m_vToPath, m_paths);
			if (path == m_path0 || path == m_path1) {
				int nIdx = -1;
				for (int i = 0; i < 4; ++i) {
					int idx1 = rotate(tile[2 * i], rot);
					int idx2 = rotate(tile[2 * i + 1], rot);
					if (idx1 == idx || idx2 == idx) {
						nIdx = idx1 ^ idx2 ^ idx;
						break;
					}
				}
				int add = 1 - idx % 2;
				if ((idx + 1 + add) % 8 == nIdx || (idx + 2 + add) % 8 == nIdx) {
					return true;
				};
			}
		}
		return false;
	}
	int rotate(int x, int rot) const {
		return (x + 2 * rot) % 8;
	}
	void mergePaths(
		int x,
		int y,
		map<int, int> &vToPath,
		vector<Path> &paths,
		int &path0,
		int &path1) {
			int xPath = getPath(x, vToPath, paths);
			int yPath = getPath(y, vToPath, paths);
			int s0 = paths[xPath].source;
			int e0 = paths[xPath].target;
			int s1 = paths[yPath].source;
			int e1 = paths[yPath].target;
			if (xPath == yPath) {  // cycle
				paths[xPath] = Path();
				vToPath[s0] = -1;
				vToPath[e0] = -1;
				vToPath[s1] = -1;
				vToPath[e1] = -1;
				return;
			}
			int s = s0 != x && s0 != y ? s0 : e0;
			int e = s1 != x && s1 != y ? s1 : e1;
			Path newPath(s, e, paths[xPath].length + 1 + paths[yPath].length);
			vToPath[s] = xPath;
			vToPath[e] = xPath;
			if (s0 != s && s0 != e) {
				vToPath[s0] = -1;
			}
			if (e0 != s && e0 != e) {
				vToPath[e0] = -1;
			}
			if (s1 != s && s1 != e) {
				vToPath[s1] = -1;
			}
			if (e1 != s && e1 != e) {
				vToPath[e1] = -1;
			}
			paths[xPath] = newPath;
			paths[yPath] = Path();
			if (path0 == yPath) {
				path0 = xPath;
			}
			if (path1 == yPath) {
				path1 = xPath;
			}
	}
	void addEdges(int row, int col, int rot, const Tile &tile) {
		for (int i = 0; i < 4; ++i) {
			int x = getNode(row, col, rotate(tile[2 * i], rot));
			int y = getNode(row, col, rotate(tile[2 * i + 1], rot));
			int xPath = getPath(x, m_vToPath, m_paths);
			int yPath = getPath(y, m_vToPath, m_paths);
			mergePaths(x, y, m_vToPath, m_paths, m_path0, m_path1);
		}
	}

	bool isFirstFibre(int row, int col) {
		for (int idx = 0; idx < 8; ++idx) {
			int v = getNode(row, col, idx);
			int c = getPath(v, m_vToPath, m_paths);
			if (c == m_path0) {
				return true;
			}
		}
		return false;
	}

	int getAdditionalLength(int row, int col, int rot, const Tile &tile) {
		int res = 0;
		for (int i = 0; i < 4; ++i) {
			int idx0 = rotate(tile[2 * i], rot);
			int idx1 = rotate(tile[2 * i + 1], rot);
			int x = getPath(getNode(row, col, idx0), m_vToPath, m_paths);
			int y = getPath(getNode(row, col, idx1), m_vToPath, m_paths);
			if (x == m_path0 || x == m_path1) {
				res += m_paths[y].length;
			} else if (y == m_path0 || y == m_path1) {
				res += m_paths[x].length;
			}
		}
		return res;
	}

	void addWeight(double &w, double w0, double X, double p, double c) {
		int sign = w0 < 0 ? -1 : 1;
		w0 = fabs(w0);
		w = w * X + sign * c * pow(w0, p);
	}

	double getDistOfNextCell(int row, int col, int rot, const Tile &tile) {
		for (int i = 0; i < 4; ++i) {
			int idx0 = rotate(tile[2 * i], rot);
			int idx1 = rotate(tile[2 * i + 1], rot);
			int x = getNode(row, col, idx0);
			int y = getNode(row, col, idx1);
			int cx = getPath(x, m_vToPath, m_paths);
			int cy = getPath(y, m_vToPath, m_paths);
			if (cy == m_path0 || cy == m_path1) {
				swap(cx, cy);
				swap(x, y);
			}
			if (cx == m_path0 || cx == m_path1) {
				int path = cx == m_path0 ? cy : cx;
				int ny = m_paths[path].source ^ m_paths[path].target ^ y;
				int nrow, ncol, idx;
				getCell(ny, nrow, ncol, idx);
				return max(abs(nrow - m_N), abs(ncol - m_N));
				//return abs(nrow - m_N) + abs(ncol - m_N);
				//return sqr((double)nrow - m_N) + sqr((double)ncol - m_N);
			}
		}
		return 0;
	}

	int getCellDegree(int row, int col) {
		int deg = 0;
		for (int t = 0; t < 4; ++t) {
			int i = row + di[t];
			int j = col + dj[t];
			deg += m_used.check(i, j);
		}
		return deg;
	}

	int getDegreeOfNextCell(int row, int col, int rot, const Tile &tile) {
		for (int i = 0; i < 4; ++i) {
			int idx0 = rotate(tile[2 * i], rot);
			int idx1 = rotate(tile[2 * i + 1], rot);
			int x = getNode(row, col, idx0);
			int y = getNode(row, col, idx1);
			int cx = getPath(x, m_vToPath, m_paths);
			int cy = getPath(y, m_vToPath, m_paths);
			if (cy == m_path0 || cy == m_path1) {
				swap(cx, cy);
				swap(x, y);
			}
			if (cx == m_path0 || cx == m_path1) {
				int path = cx == m_path0 ? cy : cx;
				int ny = m_paths[path].source ^ m_paths[path].target ^ y;
				int nrow, ncol, idx;
				getCell(ny, nrow, ncol, idx);
				for (int t = 0; t < 4; ++t) {
					int i = nrow + di[t];
					int j = ncol + dj[t];
					for (int idx = 0; idx < 8; ++idx) {
						if (!m_used.check(i, j) && getNode(i, j, idx) == ny) {
							return getCellDegree(i, j);
						}
					}
				}
			}
		}
		return 0;
	}

	int compareMoves(int row1, int col1, int rot1, int row2, int col2, int rot2, const Tile &tile) {
		double w = 0;
		const double X = 1.41;

		int cycles1 = getCyclesAmount(row1, col1, rot1, tile);
		int cycles2 = getCyclesAmount(row2, col2, rot2, tile);
		if (cycles1 != cycles2) {
			addWeight(w, cycles1 - cycles2, X, 1, 2);
		}

		const int MIN_ALLOWED_MAX_DIST = m_path0 == m_path1 ? 10 : 2;
		int maxDist1 = getMaxDist(row1, col1, rot1, tile);
		int maxDist2 = getMaxDist(row2, col2, rot2, tile);
		if (maxDist1 <= MIN_ALLOWED_MAX_DIST && maxDist2 <= MIN_ALLOWED_MAX_DIST && maxDist1 != maxDist2) {
			addWeight(w, maxDist2 - maxDist1, X, 1, 1);
		}

		const int MIN_DIST_TO_CHECK = (int)sqrt((double)m_N) / 10;
		double dist1 = getDistOfNextCell(row1, col1, rot1, tile);
		double dist2 = getDistOfNextCell(row2, col2, rot2, tile);
		if (dist1 > MIN_DIST_TO_CHECK && dist2 > MIN_DIST_TO_CHECK && dist1 != dist2) {
			addWeight(w, dist1 - dist2, X, 1, 1);
		}

		const int ADD_LENGTH_STEP = m_N / 2;
		if (m_step > ADD_LENGTH_STEP) {
			int addLength1 = getAdditionalLength(row1, col1, rot1, tile);
			int addLength2 = getAdditionalLength(row2, col2, rot2, tile);
			if (addLength1 != addLength2) {
				addWeight(w, addLength2 - addLength1, X, 1, 1);
			}
		}

		const int TURN_STEP = 2;
		if (m_step > TURN_STEP) {
			bool isTurnToLeft1 = isTurnLeft(row1, col1, rot1, tile);
			bool isTurnToLeft2 = isTurnLeft(row2, col2, rot2, tile);
			if (isTurnToLeft1 != isTurnToLeft2) {
				addWeight(w, (int)isTurnToLeft2 - (int)isTurnToLeft1, X, 1, 1);
			}
		}

		const int MERGE_STEP = m_N / 100;
		if (m_step > MERGE_STEP) {
			bool isMerge1 = isMergeTwoFibres(row1, col1, rot1, tile);
			bool isMerge2 = isMergeTwoFibres(row2, col2, rot2, tile);
			if (isMerge1 != isMerge2) {
				addWeight(w, (int)isMerge2 - (int)isMerge1, X, 1, 1);
			}
		}

		const int MIN_CONSIDERED_DEGREE = 2;
		int degree1 = getDegreeOfNextCell(row1, col1, rot1, tile);
		int degree2 = getDegreeOfNextCell(row2, col2, rot2, tile);
		if ((degree1 >= MIN_CONSIDERED_DEGREE || degree2 >= MIN_CONSIDERED_DEGREE) && degree1 != degree2) {
			addWeight(w, degree2 - degree1, X, 1, 1);
		}

		return w < 0 ? -1 : w > 0 ? 1 : 0;
	}

	void getCell(int v, int &row, int &col, int &idx) {
		idx = v % 4;
		col = v / 4 % getWidth();
		row = v / 4 / getWidth();
	}

	bool findNextMove(int &row, int &col, int &rot, const Tile &tile) {
		row = -1;
		col = -1;
		rot = -1;
		vector<Cell> candidateCells;
		queue<int> q;
		set<int> endVertices;
		endVertices.insert(m_paths[m_path0].source);
		endVertices.insert(m_paths[m_path0].target);
		endVertices.insert(m_paths[m_path1].source);
		endVertices.insert(m_paths[m_path1].target);
		for (set<int>::iterator it = endVertices.begin(); it != endVertices.end(); ++it) {
			int v = *it;
			int i;
			int j;
			int tempIdx;
			getCell(v, i, j, tempIdx);
			vector<pair<int, int> > cells;
			cells.push_back(make_pair(i, j));
			for (int t = 0; t < 4; ++t) {
				int nt = t;
				int ni = i + di[nt];
				int nj = j + dj[nt];
				cells.push_back(make_pair(ni, nj));
			}
			for (int t = 0; t < cells.size(); ++t) {
				int ni = cells[t].first;
				int nj = cells[t].second;
				if (!m_used.check(ni, nj)) {
					bool ok = false;
					for (int idx = 0; idx < 8; ++idx) {
						int v = getNode(ni, nj, idx);
						int c = getPath(v, m_vToPath, m_paths);
						int c0 = m_path0;
						int c1 = m_path1;
						if (c == c0 || c == c1) {
							ok = true;
							break;
						}
					}
					if (ok) {
						candidateCells.push_back(Cell(ni, nj));
					}
				}
			}
		}
		if (candidateCells.empty()) {
			return false;
		}
		row = candidateCells.front().row;
		col = candidateCells.front().col;
		rot = 0;
		for (int i = 0; i < candidateCells.size(); ++i) {
			for (int nRot = 0; nRot < 4; ++nRot) {
				int nRow = candidateCells[i].row;
				int nCol = candidateCells[i].col;
				if (compareMoves(row, col, rot, nRow, nCol, nRot, tile) > 0) {
					row = nRow;
					col = nCol;
					rot = nRot;
				}
			}
		}
		return true;
	}

	int getWidth() {
		return m_N + m_N + 5;
	}

	int getNode(int r, int c, int idx) {
		if (idx == 7) {
			return getNode(r, c - 1, 2);
		}
		if (idx == 6) {
			return getNode(r, c - 1, 3);
		}
		if (idx == 5) {
			return getNode(r + 1, c, 0);
		}
		if (idx == 4) {
			return getNode(r + 1, c, 1);
		}
		return (r * getWidth() + c) * 4 + idx;
	}
	void getNodes(int r1, int c1, int r2, int c2, int &path0, int &path1) {
		int v0, v1;
		if (r1 > r2 || c1 > c2) {
			swap(r1, r2);
			swap(c1, c2);
		}
		if (r1 < r2) {
			v0 = getNode(r2, c2, 0);
			v1 = getNode(r2, c2, 1);
		} else {
			v0 = getNode(r1, c1, 2);
			v1 = getNode(r1, c1, 3);
		}
		path0 = getPath(v0, m_vToPath, m_paths);
		path1 = getPath(v1, m_vToPath, m_paths);
	}
	string makeString(int row, int col, int rot) {
		stringstream out;
		out << row << " " << col << " " << rot;
		return out.str();
	}
};


int main() {
	int N;
	scanf("%d", &N);
	Tile firstTile(8);
	ofstream finp("input.txt");
	finp << N << endl;
	for (int i = 0; i < 8; ++i) {
		scanf("%d", &firstTile[i]);
		finp << firstTile[i] << endl;
	}
	TwistedGame twistedGame;
	twistedGame.init(N, firstTile);
	int firstElement;
	int tileId = 0;
	while (scanf("%d", &firstElement) != EOF) {
		finp << firstElement << endl;
		++tileId;
		if (tileId % 1000 == 0) {
			cerr << "tile #" << tileId << endl;
		}

		Tile tile(8);
		tile[0] = firstElement;
		for (int i = 1; i < 8; ++i) {
			scanf("%d", &tile[i]);
			finp << tile[i] << endl;
		}
		string ret = twistedGame.placeTile(tile);
		printf("%s\n", ret.c_str());
		fflush(stdout);
		if (ret == GIVE_UP) {
			break;
		}
	}
	cerr << "end of input" << endl;
	return 0;
}

